﻿using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Reflection;

namespace InnovTech.Util.ComEx
{

  
    public class ComLocalServer
    {

        #region singleton

        /// <summary>
        /// Reprensents the Singleton instance
        /// </summary>
        private static ComLocalServer _instance;

        /// <summary>
        /// Gets the singleton instance of this Server.
        /// </summary>
        public static ComLocalServer Instance
        {
            get { if (_instance == null) _instance = new ComLocalServer(); return _instance; }
        }


        #endregion singletion

        #region Fields

        private List<int> _registeredTypeCookies;

        private object _syncRoot = new Object(); // For thread-sync in lock
        private bool _bRunning = false; // Whether the server is running

        // The ID of the thread that runs the message loop
        private uint _nMainThreadID = 0;

        // The lock count (the number of active COM objects) in the server
        private int _nLockCnt = 0;

        // The timer to trigger GC every 5 seconds
        private Timer _gcTimer;

        #endregion


        #region Properties


        public Dictionary<Assembly, IEnumerable<Type>> TypesToRegister
        {
            get;
            set;
        }

        private RegistrationConnectionType _connectionType = RegistrationConnectionType.MultipleUse;
        public RegistrationConnectionType ConnectionType
        {
            get
            {
                return _connectionType;
            }
            set
            {
                _connectionType = value;
            }
        }

        
        #endregion


        #region Constructors

        private ComLocalServer()
        {
        }

        #endregion


        #region Methods

        #region private
        /// <summary>
        /// The method is call every 5 seconds to GC the managed heap after 
        /// the COM server is started.
        /// </summary>
        /// <param name="stateInfo"></param>
        private static void GarbageCollect(object stateInfo)
        {
            GC.Collect();   // GC
        }

        #endregion private

        #region public
        /// <summary>
        /// Increase the lock count
        /// </summary>
        /// <returns>The new lock count after the increment</returns>
        /// <remarks>The method is thread-safe.</remarks>
        public int Lock()
        {
            return Interlocked.Increment(ref _nLockCnt);
        }

        /// <summary>
        /// Decrease the lock count. When the lock count drops to zero, post 
        /// the WM_QUIT message to the message loop in the main thread to 
        /// shut down the COM server.
        /// </summary>
        /// <returns>The new lock count after the increment</returns>
        public int Unlock()
        {
            int nRet = Interlocked.Decrement(ref _nLockCnt);

            // If lock drops to zero, attempt to terminate the server.
            if (nRet <= 0)
            {
                // Post the WM_QUIT message to the main thread
                Win32Native.PostThreadMessage(_nMainThreadID,
                    Win32Native.WM_QUIT, UIntPtr.Zero, IntPtr.Zero);
            }

            return nRet;
        }

        /// <summary>
        /// Get the current lock count.
        /// </summary>
        /// <returns></returns>
        public int GetLockCount()
        {
            return _nLockCnt;
        }


        #endregion public

        #endregion





        public void Run(Dictionary<Assembly, IEnumerable<Type>> typesToRegister, ServerOperation op, RegistrationConnectionType connectionType)
        {
            TypesToRegister = typesToRegister;
            ConnectionType = connectionType;
            Run(op);
        }


        public void Run(ServerOperation op)
        {
            lock (_syncRoot) // Ensure thread-safe
            {
                // If the server is running, return directly.
                if (_bRunning)
                {

                    return;
                }
                // Indicate that the server is running now.
                _bRunning = true;
            }

            try
            {
                if (TypesToRegister == null || TypesToRegister.Count == 0)
                    throw new DotNetComException("No COM Type to Register");

                if (op == ServerOperation.UnregisterAssembly)
                {
                    UnregisterComTypes();
                    UnregisterAssemblies();
                    return;
                }

                if (op == ServerOperation.RegisterAssembly)
                    RegisterAssemblies();

                RegisterComTypes();


                // Records the ID of the thread that runs the COM server so that 
                // the server knows where to post the WM_QUIT message to exit the 
                // message loop.
                _nMainThreadID = Win32Native.GetCurrentThreadId();

                // Records the count of the active COM objects in the server. 
                // When _nLockCnt drops to zero, the server can be shut down.
                _nLockCnt = 0;

                // Start the GC timer to trigger GC every 5 seconds.
                _gcTimer = new Timer(new TimerCallback(GarbageCollect), null,
                    5000, 5000);



                // Run the message loop.
                MSG msg;
                while (Win32Native.GetMessage(out msg, IntPtr.Zero, 0, 0))
                {
                    //NativeMethod.TranslateMessage(ref msg);
                    Win32Native.DispatchMessage(ref msg);
                }

                // Call PostMessageLoop to revoke the registration.

                UnregisterComTypes();

                // Dispose the GC timer.
                if (_gcTimer != null)
                {
                    _gcTimer.Dispose();
                }

                // Wait for any threads to finish.
                Thread.Sleep(1000);
            }
            finally
            {
                _bRunning = false;
            }
            
        }

        private void RegisterComTypes()
        {
            if (TypesToRegister == null)
                throw new DotNetComException("There is no assembly to register as COM");

            _registeredTypeCookies = new List<int>();

            foreach (Assembly assembly in TypesToRegister.Keys)
            {
                IEnumerable<Type> types = TypesToRegister[assembly];
                int[] cookies = DotNetComEx.RegisterComTypes(assembly, types, ConnectionType);
                _registeredTypeCookies.AddRange(cookies);
            }
        }

        private void UnregisterComTypes()
        {
            DotNetComEx.UnregisterComTypes(_registeredTypeCookies);
            _registeredTypeCookies = null;
        }

        private void UnregisterAssemblies()
        {
            if (TypesToRegister == null)
                throw new DotNetComException("There is no assembly to unregister as COM");

            foreach (Assembly assembly in TypesToRegister.Keys)
            {
                DotNetComEx.UnregisterAssembly(assembly);
            }
        }

        private void RegisterAssemblies()
        {
            if (TypesToRegister == null)
                throw new DotNetComException("There is no assembly to register as COM");

            foreach (Assembly assembly in TypesToRegister.Keys)
            {
                DotNetComEx.RegisterAssembly(assembly);
            }
        }

        
    }

    

    
    
}
